﻿using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace Magiq.Support {
    public static class ExpressionExtensions {
        /// <summary>
        /// Retrieves the property represented by the specified expression
        /// </summary>
        /// <param name="expression">The expression.</param>
        /// <returns></returns>
        public static PropertyInfo ToPropertyInfo(this LambdaExpression expression) {
            var memberExpression = MemberExpression(expression);
            return (PropertyInfo) memberExpression.Member;
        }

        public static MethodInfo ToMethodInfo(this LambdaExpression expression) {
            var memberExpression = MethodCallExpression(expression);
            return memberExpression.Method;
        }

        public static MemberExpression MemberExpression(LambdaExpression expression) {
            return expression.Body.MemberExpression();
        }

        public static MethodCallExpression MethodCallExpression(this Expression expression) {
            MethodCallExpression methodCallExpression = null;
            switch (expression.NodeType) {
                case ExpressionType.Lambda: {
                    var lambda = ((LambdaExpression) expression);
                    methodCallExpression = lambda.Body as MethodCallExpression;
                }
                    break;
            }

            if (methodCallExpression == null) throw new ArgumentException("Not a method call", "expression");
            return methodCallExpression;
        }

        public static ParameterExpression GetParameter(this Expression expression) {
            if (expression.Is<ParameterExpression>())
                return (ParameterExpression) expression;

            if (expression.Is<LambdaExpression>())
                return GetParameter(((LambdaExpression) expression).Body);
            if (expression.Is<MemberExpression>())
                return ((MemberExpression) expression).Expression.GetParameter();
            if (expression.Is<UnaryExpression>())
                return ((UnaryExpression) expression).Operand.GetParameter();
            if (expression.Is<MethodCallExpression>())
                return ((MethodCallExpression) expression).Object.GetParameter();
            if (expression.Is<BinaryExpression>()) {
                var binary = (BinaryExpression) expression;
                return binary.Left.GetParameter() ?? binary.Right.GetParameter();
            }
            return null;
        }

        public static LambdaExpression PreviousExpressionChain(this LambdaExpression self) {
            return self.PreviousExpressionChain(false);
        }

        public static LambdaExpression PreviousExpressionChain(this Expression self, bool includeRoot) {
            Expression e = null;
            if (self is LambdaExpression)
                return ((LambdaExpression) self).Body.PreviousExpressionChain(includeRoot);

            if (self is UnaryExpression)
                return ((UnaryExpression) self).Operand.PreviousExpressionChain(includeRoot);

            if (self is MemberExpression)
                e = ((MemberExpression) self).Expression;

            if (e == null || (!includeRoot && e is ParameterExpression))
                return null;

            return Expression.Lambda(e, self.GetParameter());
        }


        public static LambdaExpression LastExpressionChain(this Expression self) {
            Expression e = null;
            if (self is LambdaExpression)
                return ((LambdaExpression) self).Body.LastExpressionChain();

            if (self is UnaryExpression)
                return ((UnaryExpression) self).Operand.LastExpressionChain();
            if (self is MemberExpression)
                e = ((MemberExpression) self).Expression;

            if (e == null)
                return null;

            return Expression.Lambda(e, self.GetParameter());
        }


        public static MemberExpression MemberExpression(this Expression self) {
            if (self is LambdaExpression)
                return ((LambdaExpression) self).Body.MemberExpression();

            if (self is UnaryExpression)
                return ((UnaryExpression) self).Operand.MemberExpression();

            if (self is MemberExpression)
                return (MemberExpression) self;

            return null;
        }

        private static Expression ConcreteExpression(this Expression self) {
            if (self is LambdaExpression)
                return ((LambdaExpression) self).Body.ConcreteExpression();

            if (self is UnaryExpression)
                return (((UnaryExpression) self).Operand.ConcreteExpression());

            if (self is MemberExpression)
                return ((MemberExpression) self).Expression.ConcreteExpression();

            return self;
        }

        public static bool IsConstant(this Expression self) {
            return self.ConcreteExpression() is ConstantExpression;
        }

        public static LambdaExpression ChangeParameters(this LambdaExpression self,
                                                        params ParameterExpression[] parameters) {
            return Expression.Lambda(Expression.Invoke(self, parameters.Cast<Expression>()), parameters);
        }
    }
}